/*
 * Copyright 2010-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
 * that can be found in the LICENSE file.
 */

#include "Cleaner.h"
#include "std_support/Atomic.hpp"
#include "Memory.h"
#include "Runtime.h"
#include "Worker.h"

// Defined in Cleaner.kt
extern "C" void Kotlin_CleanerImpl_shutdownCleanerWorker(KInt, bool);
extern "C" KInt Kotlin_CleanerImpl_createCleanerWorker();

namespace {

struct CleanerImpl {
    ObjHeader header;
    KNativePtr cleanerStablePtr;
};

constexpr KInt kCleanerWorkerUninitialized = 0;
constexpr KInt kCleanerWorkerInitializing = -1;
constexpr KInt kCleanerWorkerShutdown = -2;

std::atomic globalCleanerWorker = kCleanerWorkerUninitialized;

void disposeCleaner(CleanerImpl* thiz) {
    auto worker = globalCleanerWorker.load();
    RuntimeAssert(
            worker != kCleanerWorkerUninitialized && worker != kCleanerWorkerInitializing,
            "Cleaner worker must've been initialized by now");
    if (worker == kCleanerWorkerShutdown) {
        if (Kotlin_cleanersLeakCheckerEnabled()) {
            konan::consoleErrorf(
                    "Cleaner %p was disposed during program exit\n"
                    "Use `Platform.isCleanersLeakCheckerActive = false` to avoid this check.\n",
                    thiz);
            RuntimeCheck(false, "Terminating now");
        }
        return;
    }

    RuntimeAssert(worker > 0, "Cleaner worker must be fully initialized here");

    bool result = WorkerSchedule(worker, thiz->cleanerStablePtr);
    RuntimeAssert(result, "Couldn't find Cleaner worker");
}

} // namespace

RUNTIME_NOTHROW void DisposeCleaner(KRef thiz) {
    try {
        disposeCleaner(reinterpret_cast<CleanerImpl*>(thiz));
    } catch (...) {
        // A trick to terminate with unhandled exception. This will print a stack trace
        // and write to iOS crash log.
        std::terminate();
    }
}

void ShutdownCleaners(bool executeScheduledCleaners) {
    KInt worker = 0;
    {
        // This loop may spin waiting for a proper worker state. Switch to the native thread state.
        kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
        do {
            worker = globalCleanerWorker.load();
            RuntimeAssert(worker != kCleanerWorkerShutdown, "Cleaner worker must not be shutdown twice");
            if (worker == kCleanerWorkerUninitialized) {
                if (!globalCleanerWorker.compare_exchange_weak(worker, kCleanerWorkerShutdown)) {
                    // Someone is trying to initialize the worker. Try again.
                    continue;
                }
                // worker was never initialized. Just return.
                return;
            }
            if (worker == kCleanerWorkerInitializing) {
                // Someone is trying to initialize the worker. Try again.
                continue;
            }

            // Worker is in some proper state.
            break;

        } while (true);
    }

    RuntimeAssert(worker > 0, "Cleaner worker must be fully initialized here");

    globalCleanerWorker.store(kCleanerWorkerShutdown);
    Kotlin_CleanerImpl_shutdownCleanerWorker(worker, executeScheduledCleaners);
    WaitNativeWorkerTermination(worker);
}

extern "C" KInt Kotlin_CleanerImpl_getCleanerWorker() {
    KInt worker = 0;
    // This loop may spin waiting for a proper worker state. Switch to the native thread state.
    kotlin::ThreadStateGuard guard(kotlin::ThreadState::kNative);
    do {
        worker = globalCleanerWorker.load();
        RuntimeAssert(worker != kCleanerWorkerShutdown, "Cleaner worker must not have been shutdown");
        if (worker == kCleanerWorkerUninitialized) {
            if (!globalCleanerWorker.compare_exchange_weak(worker, kCleanerWorkerInitializing)) {
                // Someone else is trying to initialize the worker. Try again.
                continue;
            }
            worker = kotlin::CallWithThreadState<kotlin::ThreadState::kRunnable>(Kotlin_CleanerImpl_createCleanerWorker);
            if (!kotlin::std_support::atomic_compare_exchange_strong(globalCleanerWorker, kCleanerWorkerInitializing, worker)) {
                RuntimeCheck(false, "Someone interrupted worker initializing");
            }
            // Worker is initialized.
            break;
        }
        if (worker == kCleanerWorkerInitializing) {
            // Someone is trying to initialize the worker. Try again.
            continue;
        }

        // Worker is in some proper state.
        break;
    } while (true);

    RuntimeAssert(worker > 0, "Cleaner worker must be fully initialized here");

    return worker;
}

void ResetCleanerWorkerForTests() {
    globalCleanerWorker.store(kCleanerWorkerUninitialized);
}
